﻿/** Copyright © 2010-2015 Vahid Jalili
 * 
 * This file is part of ReShapes project.
 * ReShapes is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation,
 * either version 3 of the License, or (at your option) any later version.
 * ReShapes is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License along with Foobar. If not, see http://www.gnu.org/licenses/.
 **/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace ReShapes
{
    class Iteration
    {
        #region Variables

        public int
            Lambda = 0,
            Omega = 0,
            LN = 0,
            LM = 0,
            Neighbor_Range = 0;

        private int 
            N = 0, 
            M = 0, 
            Total_Landmark_Points_A = 0, 
            Total_Landmark_Points_B = 0;

        private double
            d = 10.0;

        private List<double> _LOG_ = new List<double>();

        const double _180_over_PI = 180.0 / Math.PI;

        public class Result
        {
            public double Match_Score { set; get; }
            public int C_Shitfs { set; get; }
            public double Rotation_Angle { set; get; }

            // followings could be eliminated, they give only extra info 
            public int Matched_Landmarks_Count { set; get; }
            public double Matched_Landmarks_Ratio { set; get; }
            public int Matched_Regions { set; get; }
            public double Matched_Regions_Ratio { set; get; }
        }
        public List<Result> Results = new List<Result>();


        public List<List<int>> Figure_A_meets = new List<List<int>>();
        public List<List<int>> Figure_B_meets = new List<List<int>>();
        public SortedDictionary<double, int> Figure_A_landmarkDistribution = new SortedDictionary<double, int>();

        public List<List<int>> Fig_A_Gamma = new List<List<int>>();
        public List<List<int>> Fig_B_Gamma = new List<List<int>>();
        public SortedDictionary<double, int> Figure_B_landmarkDistribution = new SortedDictionary<double, int>();

        public int[,] Figure_A_Crop;
        public int[,] Figure_B_Crop;

        public List<int> Alpha = new List<int>();

        private Hashtable Checked_C_Shifts = new Hashtable();

        #endregion

        public void Start_Reasoning(bool Is_Initial_Run)
        {
            N = (int)Math.Pow(2.0, LN);
            M = (int)Math.Pow(2.0, LM);

            Checked_C_Shifts.Clear();

            double stdA = Setup_Figure_A_Gamma();
            double stdB = Setup_Figure_B_Gamma();
            d = Math.Abs(stdA - stdB);
            Count_Total_Landmark_Points();

            if (Neighbor_Range == 0)
            {
                switch (Is_Initial_Run)
                {
                    case true:
                        for (int C_Shift = 0; C_Shift < N; C_Shift++)
                            Do_Circular_Shift_and_Compare__NO__Neighbors(C_Shift);
                        break;

                    case false:
                        for (int i = 0; i < Lambda; i++)
                            for (int j = 0; j < Alpha.Count; j++)
                            {
                                Do_Circular_Shift_and_Compare__NO__Neighbors(((int)Math.Pow(2, LN - Omega) * Alpha[j]) + i);
                                Do_Circular_Shift_and_Compare__NO__Neighbors(((int)Math.Pow(2, LN - Omega) * Alpha[j]) - i);
                            }
                        break;
                }
            }
            else if (Neighbor_Range < N && Neighbor_Range < M)
            {
                Process_LOG();

                switch (Is_Initial_Run)
                {
                    case true:
                        for (int C_Shift = 0; C_Shift < N; C_Shift++)
                            Do_Circular_Shift_and_Compare_With_Neighbors(C_Shift);
                        break;

                    case false:
                        for (int i = 0; i < Lambda; i++)
                            for (int j = 0; j < Alpha.Count; j++)
                            {
                                Do_Circular_Shift_and_Compare_With_Neighbors(((int)Math.Pow(2, LN - Omega) * Alpha[j]) + i);
                                Do_Circular_Shift_and_Compare_With_Neighbors(((int)Math.Pow(2, LN - Omega) * Alpha[j]) - i);
                            }
                        break;
                }
            }

            Results_Comparer RC = new Results_Comparer();
            Results.Sort(RC);
        }

        private void Count_Total_Landmark_Points()
        {
            Total_Landmark_Points_A = 0;
            Total_Landmark_Points_B = 0;

            for (int n = 0; n < N; n++)
                for (int m = 0; m < M; m++)
                    Total_Landmark_Points_A += Fig_A_Gamma[n][m];

            for (int n = 0; n < N; n++)
                for (int m = 0; m < M; m++)
                    Total_Landmark_Points_B += Fig_B_Gamma[n][m];
        }

        private void Do_Circular_Shift_and_Compare__NO__Neighbors(int C_Shift)
        {
            int total_non_zero_segments = 0;

            if (C_Shift < 0)
                C_Shift = N + C_Shift;

            // Some cases are observed where the C_Shift is less than 0 
            // even after the fist
            if (C_Shift < 0)
                C_Shift = N + C_Shift;

            if (C_Shift > N)
                C_Shift = C_Shift % N;

            if (C_Shift < 0)
            { }

            if (!Checked_C_Shifts.Contains(C_Shift))
            {
                Checked_C_Shifts.Add(C_Shift, "Hamed");

                // could be eliminated, it gives only extra info 
                int
                    Matched_Regions_Count = 0,
                    Matched_LPoints_Count = 0;

                double
                    Match_Score = 0.0;

                for (int n = 0; n < N - C_Shift; n++)
                {
                    for (int m = 0; m < M; m++)
                    {
                        if (Fig_A_Gamma[n][m] + Fig_B_Gamma[n + C_Shift][m] != 0)
                        {
                            Match_Score +=
                                1.0 -
                                (double)(Math.Abs(Fig_A_Gamma[n][m] - Fig_B_Gamma[n + C_Shift][m])
                                /
                                (double)(Fig_A_Gamma[n][m] + Fig_B_Gamma[n + C_Shift][m]));
                        }


                        // could be eliminated, it gives only extra info 
                        // d is set in call to d = d / math.pow(2, l - omega)
                        if (Fig_A_Gamma[n][m] != 0 &&
                            Fig_B_Gamma[n + C_Shift][m] != 0)
                        {
                            total_non_zero_segments++;
                            if (Math.Abs(Fig_A_Gamma[n][m] - Fig_B_Gamma[n + C_Shift][m]) <= d)
                            {
                                Matched_Regions_Count++;
                                Matched_LPoints_Count += Fig_A_Gamma[n][m] + Fig_B_Gamma[n + C_Shift][m];
                            }
                        }
                    }
                }

                for (int n = N - C_Shift; n < N; n++)
                {
                    for (int m = 0; m < M; m++)
                    {
                        if (Fig_A_Gamma[n][m] + Fig_B_Gamma[n + C_Shift - N][m] != 0)
                        {
                            Match_Score +=
                                1.0 -
                                (double)(Math.Abs(Fig_A_Gamma[n][m] - Fig_B_Gamma[n + C_Shift - N][m])
                                /
                                (double)(Fig_A_Gamma[n][m] + Fig_B_Gamma[n + C_Shift - N][m]));
                        }

                        // could be eliminated, it gives only extra info 
                        // d is set in call to d = d / math.pow(2, l - omega)
                        if (Fig_A_Gamma[n][m] != 0 &&
                            Fig_B_Gamma[n + C_Shift - N][m] != 0)
                        {
                            total_non_zero_segments++;
                            if (Math.Abs(Fig_A_Gamma[n][m] - Fig_B_Gamma[n + C_Shift - N][m]) <= d)
                            {
                                Matched_Regions_Count++;
                                Matched_LPoints_Count += Fig_A_Gamma[n][m] + Fig_B_Gamma[n + C_Shift - N][m];
                            }
                        }
                    }
                }

                //Save_Compare_Results(Matched_Regions_Count, Matched_LPoints_Count, C_Shift);

                Results.Add(new Result
                {
                    Match_Score = Match_Score,
                    C_Shitfs = C_Shift,
                    Rotation_Angle = (360.0 / (double)N) * (double)C_Shift,
                    Matched_Landmarks_Count = Matched_LPoints_Count,
                    Matched_Landmarks_Ratio = Math.Round((double)(Matched_LPoints_Count * 100) / (double)(Total_Landmark_Points_A + Total_Landmark_Points_B), 4),
                    Matched_Regions = Matched_Regions_Count,
                    Matched_Regions_Ratio = Math.Round((double)(Matched_Regions_Count * 100.0) / (double)(total_non_zero_segments), 4)
                });
            }
        }
        private void Do_Circular_Shift_and_Compare_With_Neighbors(int C_Shift)
        {
            int total_non_zero_segments = 0;

            if (C_Shift < 0)
                C_Shift = N + C_Shift;

            // Some cases are observed where the C_Shift is less than 0 
            // even after the fist
            if (C_Shift < 0)
                C_Shift = N + C_Shift;

            if (C_Shift > N)
                C_Shift = C_Shift % N;

            if (C_Shift < 0)
            { }

            if (!Checked_C_Shifts.Contains(C_Shift))
            {
                Checked_C_Shifts.Add(C_Shift, "Hamed");

                // could be eliminated, it gives only extra info 
                int
                    Matched_Regions_Count = 0,
                    Matched_LPoints_Count = 0;

                double
                    Match_Score = 0.0,
                    t_Match_Score = 0.0;

                for (int n = 0; n < N - C_Shift; n++)
                {
                    for (int m = 0; m < M; m++)
                    {
                        if (Fig_A_Gamma[n][m] + Fig_B_Gamma[n + C_Shift][m] != 0)
                        {
                            t_Match_Score =
                                1.0 -
                                (double)(Math.Abs(Fig_A_Gamma[n][m] - Fig_B_Gamma[n + C_Shift][m])
                                /
                                (double)(Fig_A_Gamma[n][m] + Fig_B_Gamma[n + C_Shift][m]));

                            for (int i = 1; i <= Neighbor_Range; i++)
                            {
                                t_Match_Score +=
                                    (double)(_LOG_[i - 1] * Jaccard_index_of_Neighbors(n, m, n + C_Shift, m, i));
                            }

                            Match_Score += t_Match_Score;
                        }


                        // could be eliminated, it gives only extra info 
                        // d is set in call to d = d / math.pow(2, l - omega)
                        if (Fig_A_Gamma[n][m] != 0 &&
                            Fig_B_Gamma[n + C_Shift][m] != 0)
                        {
                            total_non_zero_segments++;
                            if (Math.Abs(Fig_A_Gamma[n][m] - Fig_B_Gamma[n + C_Shift][m]) <= d)
                            {
                                Matched_Regions_Count++;
                                Matched_LPoints_Count += Fig_A_Gamma[n][m] + Fig_B_Gamma[n + C_Shift][m];
                            }
                        }
                    }
                }

                for (int n = N - C_Shift; n < N; n++)
                {
                    for (int m = 0; m < M; m++)
                    {
                        if (Fig_A_Gamma[n][m] + Fig_B_Gamma[n + C_Shift - N][m] != 0)
                        {
                            t_Match_Score =
                                1.0 -
                                (double)(Math.Abs(Fig_A_Gamma[n][m] - Fig_B_Gamma[n + C_Shift - N][m])
                                /
                                (double)(Fig_A_Gamma[n][m] + Fig_B_Gamma[n + C_Shift - N][m]));

                            for (int i = 1; i <= Neighbor_Range; i++)
                            {
                                t_Match_Score +=
                                    (double)(_LOG_[i - 1] * Jaccard_index_of_Neighbors(n, m, n + C_Shift - N, m, i));
                            }


                            Match_Score += t_Match_Score;
                        }

                        // could be eliminated, it gives only extra info 
                        // d is set in call to d = d / math.pow(2, l - omega)
                        if (Fig_A_Gamma[n][m] != 0 &&
                            Fig_B_Gamma[n + C_Shift - N][m] != 0)
                        {
                            total_non_zero_segments++;
                            if (Math.Abs(Fig_A_Gamma[n][m] - Fig_B_Gamma[n + C_Shift - N][m]) <= d)
                            {
                                Matched_Regions_Count++;
                                Matched_LPoints_Count += Fig_A_Gamma[n][m] + Fig_B_Gamma[n + C_Shift - N][m];
                            }
                        }
                    }
                }

                //Save_Compare_Results(Matched_Regions_Count, Matched_LPoints_Count, C_Shift);

                Results.Add(new Result
                {
                    Match_Score = Match_Score,
                    C_Shitfs = C_Shift,
                    Rotation_Angle = (360.0 / (double)N) * (double)C_Shift,
                    Matched_Landmarks_Count = Matched_LPoints_Count,
                    Matched_Landmarks_Ratio = Math.Round((double)(Matched_LPoints_Count * 100) / (double)(Total_Landmark_Points_A + Total_Landmark_Points_B), 4),
                    Matched_Regions = Matched_Regions_Count,
                    Matched_Regions_Ratio = Math.Round((double)(Matched_Regions_Count * 100.0) / (double)(total_non_zero_segments), 4)
                });
            }
        }

        private void Save_Compare_Results(int Matched_Regions_Count, int Matched_LPoints_Count, int C_Shift)
        {
            double Match_Ratio = 0.0;

            /*
            Match_Ratio =
                Math.Round((double)(Matched_LPoints_Count * 100) / (double)(Total_Landmark_Points_A + Total_Landmark_Points_B), 4);
            */

            Match_Ratio = Math.Round((double)(Matched_Regions_Count * 100.0) / (double)(N * M), 3);

            //Matches[(int)Math.Round(Match_Ratio)].Add(new double[] { Match_Ratio, C_Shift, (360.0 / N) * C_Shift });
        }

        private double Setup_Figure_A_Gamma()
        {
            Fig_A_Gamma.Clear();

            bool Landmark_Point_Discarded = true;
            int Number_of_Discarded_Landmark_Points = 0;

            double Unit_Vector = 0.0;
            // Unit vector is the longest vector bettwen X-axis and Y-axis
            if (((Figure_A_Crop[1, 0] - Figure_A_Crop[0, 0]) / 2.0) /*+ Figure_A_Crop[0, 0]*/ >
                ((Figure_A_Crop[1, 1] - Figure_A_Crop[0, 1]) / 2.0) /*+ Figure_A_Crop[0, 1]*/)
                Unit_Vector = ((Figure_A_Crop[1, 0] - Figure_A_Crop[0, 0]) / 2.0) /*+ Figure_A_Crop[0, 0]*/;
            else
                Unit_Vector = ((Figure_A_Crop[1, 1] - Figure_A_Crop[0, 1]) / 2.0) /*+ Figure_A_Crop[0, 1]*/;

            // Initialize Gamma Matrix 
            for (int n = 0; n < N; n++)
            {
                List<int> t = new List<int>();

                for (int m = 0; m < M; m++)
                    t.Add(new int());

                Fig_A_Gamma.Add(t);
            }


            for (int i = 0; i < Figure_A_meets.Count; i++)
            {
                double
                    Angle = Math.Atan2(Figure_A_meets[i][1], Figure_A_meets[i][0]),
                    Sqrt = Math.Abs(Math.Sqrt(Math.Pow(Figure_A_meets[i][0], 2) + Math.Pow(Figure_A_meets[i][1], 2)));

                // Convert Radian to Angle
                Angle = Angle * _180_over_PI;


                if (Angle > 360)
                    Angle = Angle - 360;
                else
                    if (Angle < 0)
                        Angle = 180 + (180 + Angle);
                    else
                        if (Angle == 360)
                            Angle = 0;


                // Since counter-clockwise rotation considered as positive angles,
                // the angle must be ABS to satisfy this condition
                //Angle = Math.Abs(Angle);

                for (int n = 1; n <= N; n++)
                {
                    if ((360.0 / N) * (n - 1) <= Angle &&
                        (360.0 / N) * (n) > Angle)
                    {
                        for (int m = 1; m <= M; m++)
                        {
                            if (((Unit_Vector / M) * (m - 1) <= Sqrt &&
                                (Unit_Vector / M) * (m) > Sqrt) ||
                                (Unit_Vector <= Sqrt))
                            {
                                Fig_A_Gamma[n - 1][m - 1]++;
                                n = N;
                                m = M;

                                Landmark_Point_Discarded = false;
                            }
                        }
                    }
                }

                if (Landmark_Point_Discarded == true)
                    Number_of_Discarded_Landmark_Points++;

                Landmark_Point_Discarded = true;
            }

            double std = Normalize(Fig_A_Gamma);
            Figure_A_landmarkDistribution = CalculateLandmarkDistribution(Fig_A_Gamma);
            return std;
        }
        private double Setup_Figure_B_Gamma()
        {
            Fig_B_Gamma.Clear();

            bool Landmark_Point_Discarded = true;
            int Number_of_Discarded_Landmark_Points = 0;

            double Unit_Vector = 0.0;
            // Unit vector is the longest vector bettwen X-axis and Y-axis
            if (((Figure_B_Crop[1, 0] - Figure_B_Crop[0, 0]) / 2.0) /*+ Figure_B_Crop[0, 0]*/ >
                ((Figure_B_Crop[1, 1] - Figure_B_Crop[0, 1]) / 2.0) /*+ Figure_B_Crop[0, 1]*/)
                Unit_Vector = ((Figure_B_Crop[1, 0] - Figure_B_Crop[0, 0]) / 2.0) /*+ Figure_B_Crop[0, 0]*/;
            else
                Unit_Vector = ((Figure_B_Crop[1, 1] - Figure_B_Crop[0, 1]) / 2.0) /*+ Figure_B_Crop[0, 1]*/;

            // Initialize Gamma Matrix 
            for (int n = 0; n < N; n++)
            {
                List<int> t = new List<int>();

                for (int m = 0; m < M; m++)
                    t.Add(new int());

                Fig_B_Gamma.Add(t);
            }


            for (int i = 0; i < Figure_B_meets.Count; i++)
            {
                double
                    Angle = Math.Atan2(Figure_B_meets[i][1], Figure_B_meets[i][0]),
                    Sqrt = Math.Abs(Math.Sqrt(Math.Pow(Figure_B_meets[i][0], 2) + Math.Pow(Figure_B_meets[i][1], 2)));

                // Convert Radian to Angle
                Angle = Angle * _180_over_PI;


                if (Angle > 360)
                    Angle = Angle - 360;
                else
                    if (Angle < 0)
                        Angle = 180 + (180 + Angle);
                    else
                        if (Angle == 360)
                            Angle = 0;


                // Since counter-clockwise rotation considered as positive angles,
                // the angle must be ABS to satisfy this condition
                //Angle = Math.Abs(Angle);

                for (int n = 1; n <= N; n++)
                {
                    if ((360.0 / N) * (n - 1) <= Angle &&
                        (360.0 / N) * (n) > Angle)
                    {
                        for (int m = 1; m <= M; m++)
                        {
                            if (((Unit_Vector / M) * (m - 1) <= Sqrt &&
                                (Unit_Vector / M) * (m) > Sqrt) ||
                                (Unit_Vector <= Sqrt))
                            {
                                Fig_B_Gamma[n - 1][m - 1]++;
                                n = N;
                                m = M;

                                Landmark_Point_Discarded = false;
                            }
                        }
                    }
                }

                if (Landmark_Point_Discarded)
                    Number_of_Discarded_Landmark_Points++;

                Landmark_Point_Discarded = true;
            }

            double std = Normalize(Fig_B_Gamma);
            Figure_B_landmarkDistribution = CalculateLandmarkDistribution(Fig_B_Gamma);
            return std;
        }
        private double Normalize(List<List<int>> gamma)
        {
            // based on coefficient of variation normalization method.

            int sum = 0;
            int counter = 0;
            for (int i = 0; i < gamma.Count; i++)
                for (int j = 0; j < gamma[i].Count; j++)
                {
                    sum += gamma[i][j];
                    counter++;
                }

            double mean = sum / (double)counter;
            double sum_of_deviation = 0;
            for (int i = 0; i < gamma.Count; i++)
                for (int j = 0; j < gamma[i].Count; j++)
                    sum_of_deviation += Math.Pow(gamma[i][j] - mean, 2);

            double variance = sum_of_deviation / counter;
            double standardDeviation = Math.Pow(variance, 0.5);
            double cv = Math.Abs(standardDeviation) / Math.Abs(mean); // coefficient of variation

            for (int i = 0; i < gamma.Count; i++)
                for (int j = 0; j < gamma[i].Count; j++)
                    gamma[i][j] = (int)(Math.Round(gamma[i][j] / cv, 1) * 10);

            return standardDeviation;
        }

        private SortedDictionary<double, int> CalculateLandmarkDistribution(List<List<int>> gamma)
        {
            var rtv = new SortedDictionary<double, int>();

            for (int i = 0; i < gamma.Count; i++)
                for (int j = 0; j < gamma[i].Count; j++)
                    if (rtv.ContainsKey(gamma[i][j]))
                        rtv[gamma[i][j]]++;
                    else
                        rtv.Add(gamma[i][j], 1);

            return rtv;
        }

        private double Jaccard_index_of_Neighbors(
            int An,
            int Am,
            int Bn,
            int Bm,
            int Nd)
        {
            // A_n : Figure A - Center - n 
            // A_m : Figure A - Center - m
            // B_n : Figure B - Center - n
            // B_m : Figure B - Center - m 
            // N_d : Neighbor Distance

            double
                J = 0.0;

            int
                An_m = C(An - Nd),
                An_p = C(An + Nd),
                Am_m = C(Am - Nd),
                Am_p = C(Am + Nd),
                Bn_m = C(Bn - Nd),
                Bn_p = C(Bn + Nd),
                Bm_m = C(Bm - Nd),
                Bm_p = C(Bm + Nd),
                i_An_m = 0,
                i_An_p = 0,
                i_Am_m = 0,
                i_Am_p = 0,
                i_Bn_m = 0,
                i_Bn_p = 0,
                i_Bm_m = 0,
                i_Bm_p = 0;


            // Up - Left
            if (Fig_A_Gamma[An_m][Am_m] + Fig_B_Gamma[Bn_m][Bm_m] != 0)
            {
                J =
                    1.0 - ((double)(Math.Abs(Fig_A_Gamma[An_m][Am_m] - Fig_B_Gamma[Bn_m][Bm_m]))
                    /
                    (double)(Fig_A_Gamma[An_m][Am_m] + Fig_B_Gamma[Bn_m][Bm_m]));
            }
            else
                J = 0.0;


            // Up 
            if (Fig_A_Gamma[An][Am_m] + Fig_B_Gamma[Bn][Bm_m] != 0)
            {
                J +=
                    1.0 - ((double)(Math.Abs(Fig_A_Gamma[An][Am_m] - Fig_B_Gamma[Bn][Bm_m]))
                    /
                    (double)(Fig_A_Gamma[An][Am_m] + Fig_B_Gamma[Bn][Bm_m]));
            }


            // Up - Right
            if (Fig_A_Gamma[An_p][Am_m] + Fig_B_Gamma[Bn_p][Bm_m] != 0)
            {
                J +=
                    1.0 - ((double)(Math.Abs(Fig_A_Gamma[An_p][Am_m] - Fig_B_Gamma[Bn_p][Bm_m]))
                    /
                    (double)(Fig_A_Gamma[An_p][Am_m] + Fig_B_Gamma[Bn_p][Bm_m]));
            }


            // Left
            if (Fig_A_Gamma[An_m][Am] + Fig_B_Gamma[Bn_m][Bm] != 0)
            {
                J +=
                    1.0 - ((double)(Math.Abs(Fig_A_Gamma[An_m][Am] - Fig_B_Gamma[Bn_m][Bm]))
                    /
                    (double)(Fig_A_Gamma[An_m][Am] + Fig_B_Gamma[Bn_m][Bm]));
            }


            // Right
            if (Fig_A_Gamma[An_p][Am] + Fig_B_Gamma[Bn_p][Bm] != 0)
            {
                J +=
                    1.0 - ((double)(Math.Abs(Fig_A_Gamma[An_p][Am] - Fig_B_Gamma[Bn_p][Bm]))
                    /
                    (double)(Fig_A_Gamma[An_p][Am] + Fig_B_Gamma[Bn_p][Bm]));
            }


            // Down - Left
            if (Fig_A_Gamma[An_m][An_p] + Fig_B_Gamma[Bn_m][Bn_p] != 0)
            {
                J +=
                    1.0 - ((double)(Math.Abs(Fig_A_Gamma[An_m][An_p] - Fig_B_Gamma[Bn_m][Bn_p]))
                    /
                    (double)(Fig_A_Gamma[An_m][An_p] + Fig_B_Gamma[Bn_m][Bn_p]));
            }


            // Down
            if (Fig_A_Gamma[An][Am_p] + Fig_B_Gamma[Bn][Bm_p] != 0)
            {
                J +=
                    1.0 - ((double)(Math.Abs(Fig_A_Gamma[An][Am_p] - Fig_B_Gamma[Bn][Bm_p]))
                    /
                    (double)(Fig_A_Gamma[An][Am_p] + Fig_B_Gamma[Bn][Bm_p]));
            }


            // Down - Right
            if (Fig_A_Gamma[An_p][Am_p] + Fig_B_Gamma[Bn_p][Bm_p] != 0)
            {
                J +=
                    1.0 - ((double)(Math.Abs(Fig_A_Gamma[An_p][Am_p] - Fig_B_Gamma[Bn_p][Bm_p]))
                    /
                    (double)(Fig_A_Gamma[An_p][Am_p] + Fig_B_Gamma[Bn_p][Bm_p]));
            }
            

            for (int i = 1; i < Nd; i++)
            {
                i_An_m = C(An - i);
                i_An_p = C(An + i);
                i_Am_m = C(Am - i);
                i_Am_p = C(Am + i);
                i_Bn_m = C(Bn - i);
                i_Bn_p = C(Bn + i);
                i_Bm_m = C(Bm - i);
                i_Bm_p = C(Bm + i);



                // ▲ ◄ 
                if (Fig_A_Gamma[i_An_m][Am_m] + Fig_B_Gamma[i_Bn_m][Bm_m] != 0)
                {
                    J +=
                        1.0 -
                        ((double)(Math.Abs(Fig_A_Gamma[i_An_m][Am_m] - Fig_B_Gamma[i_Bn_m][Bm_m]))
                        /
                        (double)(Fig_A_Gamma[i_An_m][Am_m] + Fig_B_Gamma[i_Bn_m][Bm_m]));
                }


                // ▲ ►
                if (Fig_A_Gamma[i_An_p][Am_m] + Fig_B_Gamma[i_Bn_p][Bm_m] != 0)
                {
                    J +=
                        1.0 -
                        ((double)(Math.Abs(Fig_A_Gamma[i_An_p][Am_m] - Fig_B_Gamma[i_Bn_p][Bm_m]))
                        /
                        (double)(Fig_A_Gamma[i_An_p][Am_m] + Fig_B_Gamma[i_Bn_p][Bm_m]));
                }


                // ◄ ▲
                if (Fig_A_Gamma[An_m][i_Am_m] + Fig_B_Gamma[Bn_m][i_Bm_m] != 0)
                {
                    J +=
                        1.0 -
                        ((double)(Math.Abs(Fig_A_Gamma[An_m][i_Am_m] - Fig_B_Gamma[Bn_m][i_Bm_m]))
                        /
                        (double)(Fig_A_Gamma[An_m][i_Am_m] + Fig_B_Gamma[Bn_m][i_Bm_m]));
                }


                // ◄ ▼
                if (Fig_A_Gamma[An_m][i_Am_p] + Fig_B_Gamma[Bn_m][i_Bm_p] != 0)
                {
                    J +=
                        1.0 -
                        ((double)(Math.Abs(Fig_A_Gamma[An_m][i_Am_p] - Fig_B_Gamma[Bn_m][i_Bm_p]))
                        /
                        (double)(Fig_A_Gamma[An_m][i_Am_p] + Fig_B_Gamma[Bn_m][i_Bm_p]));
                }


                // ► ▲
                if (Fig_A_Gamma[An_p][i_Am_m] + Fig_B_Gamma[Bn_p][i_Bm_m] != 0)
                {
                    J +=
                        1.0 -
                        ((double)(Math.Abs(Fig_A_Gamma[An_p][i_Am_m] - Fig_B_Gamma[Bn_p][i_Bm_m]))
                        /
                        (double)(Fig_A_Gamma[An_p][i_Am_m] + Fig_B_Gamma[Bn_p][i_Bm_m]));
                }


                // ► ▼
                if (Fig_A_Gamma[An_p][i_Am_p] + Fig_B_Gamma[Bn_p][i_Bm_p] != 0)
                {
                    J +=
                        1.0 -
                        ((double)(Math.Abs(Fig_A_Gamma[An_p][i_Am_p] - Fig_B_Gamma[Bn_p][i_Bm_p]))
                        /
                        (double)(Fig_A_Gamma[An_p][i_Am_p] + Fig_B_Gamma[Bn_p][i_Bm_p]));
                }


                // ▼ ◄ 
                if (Fig_A_Gamma[i_An_m][Am_p] + Fig_B_Gamma[i_Bn_m][Bm_p] != 0)
                {
                    J +=
                        1.0 -
                        ((double)(Math.Abs(Fig_A_Gamma[i_An_m][Am_p] - Fig_B_Gamma[i_Bn_m][Bm_p]))
                        /
                        (double)(Fig_A_Gamma[i_An_m][Am_p] + Fig_B_Gamma[i_Bn_m][Bm_p]));
                }


                // ▼ ►
                if (Fig_A_Gamma[i_An_p][Am_p] - Fig_B_Gamma[i_Bn_m][Bm_p] != 0)
                {
                    J +=
                        1.0 -
                        ((double)(Math.Abs(Fig_A_Gamma[i_An_p][Am_p] - Fig_B_Gamma[i_Bn_m][Bm_p]))
                        /
                        (double)(Fig_A_Gamma[i_An_p][Am_p] + Fig_B_Gamma[i_Bn_m][Bm_p]));
                }
            }

            return J;
        }

        /*
        private int A(int n, int m)
        {
            if (n >= 0 && n < N && m >= 0 && m < M)
                return Fig_A_Gamma[n][m];

            else if (n < 0 && m >= 0 && m < M)
                return Fig_A_Gamma[N + n][m];

            else if (n < 0 && m < 0)
                return Fig_A_Gamma[N + n][M + m];

            else if (n >= 0 && n < N && m < 0)
                return Fig_A_Gamma[n][M + m];

            else if (n >= N && m < 0)
                return Fig_A_Gamma[n - N][M + m];

            else if (n >= N && m >= 0 && m < M)
                return Fig_A_Gamma[n - N][m];

            else if (n >= N && m >= M)
                return Fig_A_Gamma[n - N][m - M];

            else if (n >= 0 && n < N && m >= M)
                return Fig_A_Gamma[n][m - M];

            else if (n < 0 && m >= M)
                return Fig_A_Gamma[N + n][m - M];

            else
                return 0;
        }
        private int B(int n, int m)
        {
            if (n >= 0 && n < N && m >= 0 && m < M)
                return Fig_B_Gamma[n][m];

            else if (n < 0 && m >= 0 && m < M)
                return Fig_B_Gamma[N + n][m];

            else if (n < 0 && m < 0)
                return Fig_B_Gamma[N + n][M + m];

            else if (n >= 0 && n < N && m < 0)
                return Fig_B_Gamma[n][M + m];

            else if (n >= N && m < 0)
                return Fig_B_Gamma[n - N][M + m];

            else if (n >= N && m >= 0 && m < M)
                return Fig_B_Gamma[n - N][m];

            else if (n >= N && m >= M)
                return Fig_B_Gamma[n - N][m - M];

            else if (n >= 0 && n < N && m >= M)
                return Fig_B_Gamma[n][m - M];

            else if (n < 0 && m >= M)
                return Fig_B_Gamma[N + n][m - M];

            else
                return 0;
        }*/

        private int C(int number)
        {
            if (number >= 0 && number < N)
                return number;

            else if (number < 0)
                return N + number;

            else if (number >= N)
                return number - N;

            else
                return 0;
        }

        private void Process_LOG()
        {
            for (int i = Neighbor_Range; i > 0; i--)
                _LOG_.Add((double)Math.Log(i + 1, Neighbor_Range + 2));
        }


        public class Results_Comparer : IComparer<Result>
        {
            public int Compare(Result A, Result B)
            {
                if (A == null)
                {
                    if (B == null)
                    {
                        // then they are equal
                        return 0;
                    }
                    else
                    {
                        // then B is greater
                        return -1;
                    }
                }
                else
                {
                    if (B == null)
                    {
                        // A is greater
                        return 1;
                    }
                    else
                    {
                        int R = A.Match_Score.CompareTo(B.Match_Score);

                        if (R != 0)
                        {
                            return -R;
                        }
                        else
                        {
                            return -(A.Rotation_Angle.CompareTo(B.Rotation_Angle));
                        }
                    }
                }
            }
        }
    }
}
